从爬虫到机器学习预测,我是如何一步一步做到的?
关键时刻,第一时间送达!
-----这是 Python数据科学的第 44 篇原创文章-----
【作者】:xiaoyu
【介绍】:一个半路转行的数据挖掘工程师
【知乎专栏】:https://zhuanlan.zhihu.com/pypcfx
本篇主要介绍如何利用scrapy爬取链x和安x客的二手房源信息。
全文1578字 | 阅读需要8分钟
- ❶ -
前情回顾
前一段时间与大家分享了北京二手房房价分析的实战项目,分为分析和建模两篇。文章发出后,得到了大家的肯定和支持,在此表示感谢。
除了数据分析,好多朋友也对爬虫特别感兴趣,想知道爬虫部分是如何实现的。本篇将分享这个项目的爬虫部分,算是数据分析的一个 "前传" 篇。
- ❷ -
爬虫前的思考
爬虫部分主要是通过爬取 链x 和 安x客 来获取二手房住房信息,因为考虑到不同网站的房源信息可以互补,所以选择了两个网站。
爬取目标是北京二手房,仅针对一个城市而言,数据量并不大。所以直接采用Scrapy
来完成爬取工作,然后将数据存储在csv格式的文件中。最终爬取结果是这样的,链x的爬虫爬取了 30000+
条数据,安x客的爬虫爬取了 3000+
条数据。不得不说链x的房源相对来讲还是比较全的。
- ❸ -
scrapy爬取链x
写一个爬虫最开始当然要想清楚需要获取什么样的数据了。本次项目对与二手房相关的数据都比较感兴趣,可以自然的想到,每个房源链接的具体详细信息是最全的。但考虑到爬虫深度影响整体爬虫效率问题,并且房源列表中数据已经能够满足基本的要求,并没有必要对每个详细链接进行深入的爬取,因此最终选择爬取房源列表。以下是房源列表(部分截图)中的房源信息:
确定以上爬取内容后,就开始爬虫部分的工作。首先在item.py
文件中定义一个子类,该子类继承了父类scrapy.Item
,然后在子类中用scrapy.Field()
定义以上信息的字段。如下代码,将所有需要的字段信息都设置好。
import scrapy
class LianjiaSpiderItem(scrapy.Item):
# define the fields for your item here like:
Id = scrapy.Field()
Region = scrapy.Field()
Garden = scrapy.Field()
Layout = scrapy.Field()
Size = scrapy.Field()
Direction = scrapy.Field()
Renovation = scrapy.Field()
Elevator = scrapy.Field()
Floor = scrapy.Field()
Year = scrapy.Field()
Price = scrapy.Field()
District = scrapy.Field()
pass
在spider
文件夹下的爬取文件(自定义)中导入所需库,如下代码:
json:json格式的转换;
scrapy:scrapy库;
logging:日志;
BeautifulSoup:使用bs4提取网页信息;
table:settings中自设的一个字典;
LianjiaSpiderItem:字段Field;
# -*- coding:utf-8 -*-
import json
import scrapy
import logging
from bs4 import BeautifulSoup
from lianjia_spider.settings import table
from lianjia_spider.items import LianjiaSpiderItem
下面进入关键部分,即爬虫部分。这部分主要需要自己做的就是如何解析,而对于爬虫是如何爬取的我们不用关心,因为它是框架已经在底层完成调度和爬取的实现,我们只要简单调用即可。
具体详细框架结构可参见:Python爬虫之Scrapy学习(基础篇)
爬虫解析部分,是在继承scrapy.Spider
父类的子类LianjiaSpider
中完成的。子类中设有三个函数,并通过callback回调逐层实现解析功能,这三个函数是:
start_requests:覆盖父类中原有函数,爬取初始url并存入消息队列中;
page_navigate:解析初始url页面,循环爬取各初始url页面下的所有页码链接;
parse:爬取每个页码下的所有详细房源链接,提取相应的字段信息,并储存至items中;
下面是三个函数的功能描述,以及代码实现。
start_requests
任何爬虫都需要有初始url,然后由初始url继续深入爬取进一步的url,直到爬取到所需数据。由于链家二手房url的特征是,由一个基础url和各大区拼音拼接组成,因此在start_requests函数中定义了base_url
的基础url,和需要拼接的北京各大区的拼音列表。
然后由这些拼接的各大区url作为所有的初始url链接,并由scrapy.Request
方法对每个链接发出异步请求,代码如下:
class LianjiaSpider(scrapy.Spider):
name = 'lianjia'
base_url = 'https://bj.lianjia.com/ershoufang/'
def start_requests(self):
district = ['dongcheng', 'xicheng', 'chaoyang', 'haidian', 'fengtai', 'shijingshan', 'tongzhou', 'changping',
'daxing', 'yizhuangkaifaqu', 'shunyi', 'fangshan', 'mentougou', 'pinggu', 'huairou',
'miyun', 'yanqing', 'yanjiao', 'xianghe']
for elem in district:
region_url = self.base_url + elem
yield scrapy.Request(url=region_url, callback=self.page_navigate)
page_navigate
对每个大区url发出异步请求后,我们需要对各大区内的所有房源列表url进行进一步的爬取,而为了能够顺利的将全部内容爬取,我们就要解决页码循环的问题。在page_navigate
函数中,使用BeautifulSoup解析html,提取页面中的pages
数据。
BeautifulSoup的具体使用方法参见:Python爬虫之BeautifulSoup解析之路
爬取获得的pages数据是json
字符串,所以需要使用json.loads
将其转换为字典格式,然后得到max_number
。最后通过for循环不断发送每个页码url的链接完成异步请求,并使用callback调用进入下一步的函数中,代码如下:
def page_navigate(self, response):
soup = BeautifulSoup(response.body, "html.parser")
try:
pages = soup.find_all("div", class_="house-lst-page-box")[0]
if pages:
dict_number = json.loads(pages["page-data"])
max_number = dict_number['totalPage']
for num in range(1, max_number + 1):
url = response.url + 'pg' + str(num) + '/'
yield scrapy.Request(url=url, callback=self.parse)
except:
logging.info("*******该地区没有二手房信息********")
parse
parse函数中,首先通过BeautifulSoup解析每个页码下的所有房源列表信息,得到house_info_list
。链x房源列表中没有所在大区信息,但是房源所在区域对于后续数据分析是很重要的,而仅通过页面解析我们没办法获取。为了获得这个字段该如何实现呢?
我们可以通过response.url
来判断,因为url正好是我们开始用所在区域拼接而成的,我们构造url的时候已经包含了大区信息。那么简单的通过辨识url中的大区拼音,就可以解决该问题了。然后使用字典table
将对应的中文所在区名映射到Region
字段中。
接下来开始对房源列表 house_info_list中的每个房源信息info
进行解析。根据链x的页面结构,可以看到,每个info下有三个不同位置的信息组,可通过class_参数进行定位。这三个位置信息分别是house_info,position_info,price_info,每组位置下包含相关字段信息。
house_info:如图包含Garden,Size,Layout,Direction,Renovation,Elevator房屋构造等字段信息;
position_info:如图包含Floor,Year,District等位置年限字段信息;
price_info:如图包含Total_price,price等字段信息;
这里说的位置不同是在前端html页面中的标签位置不同。
具体操作方法参见下面代码:
def parse(self, response):
item = LianjiaSpiderItem()
soup = BeautifulSoup(response.body, "html.parser")
#获取到所有子列表的信息
house_info_list = soup.find_all(name="li", class_="clear")
# 通过url辨认所在区域
url = response.url
url = url.split('/')
item['Region'] = table[url[-3]]
for info in house_info_list:
item['Id'] = info.a['data-housecode']
house_info = info.find_all(name="div", class_="houseInfo")[0]
house_info = house_info.get_text()
house_info = house_info.replace(' ', '')
house_info = house_info.split('/')
# print(house_info)
try:
item['Garden'] = house_info[0]
item['Layout'] = house_info[1]
item['Size'] = house_info[2]
item['Direction'] = house_info[3]
item['Renovation'] = house_info[4]
if len(house_info) > 5:
item['Elevator'] = house_info[5]
else:
item['Elevator'] = ''
except:
print("数据保存错误")
position_info = info.find_all(name='div', class_='positionInfo')[0]
position_info = position_info.get_text()
position_info = position_info.replace(' ', '')
position_info = position_info.split('/')
# print(position_info)
try:
item['Floor'] = position_info[0]
item['Year'] = position_info[1]
item['District'] = position_info[2]
except:
print("数据保存错误")
price_info = info.find_all("div", class_="totalPrice")[0]
item['Price'] = price_info.span.get_text()
yield item
对于链x的爬取,没用xpath的原因是提取一些标签实在不是很方便(只是针对于链x),因此博主采用了beautifulSoup。
- ❹ -
scrapy爬取安x客
这部分之前就有分享过,可以参见:Scrapy爬取二手房信息+可视化数据分析
以下是核心的爬虫部分,与链x爬取部分的思想一致,不同的是使用了xpath进行解析和ItemLoader对item加载储存。
# -*- coding:utf-8 -*-
import scrapy
from scrapy.loader import ItemLoader
from anjuke.items import AnjukeItem
class AnjukeSpider(scrapy.Spider):
name = 'anjuke'
custom_settings = {
'REDIRECT_ENABLED': False
}
start_urls = ['https://beijing.anjuke.com/sale/']
def start_requests(self):
base_url = 'https://beijing.anjuke.com/sale/'
for page in range(1, 51):
url = base_url + 'p' + str(page) + '/'
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
num = len(response.xpath('//*[@id="houselist-mod-new"]/li').extract())
house_info = response.xpath('//*[@id="houselist-mod-new"]')
print(house_info)
for i in range(1, num + 1):
l = ItemLoader(AnjukeItem(), house_info)
l.add_xpath('Layout', '//li[{}]/div[2]/div[2]/span[1]/text()'.format(i))
l.add_xpath('Size', '//li[{}]/div[2]/div[2]/span[2]/text()'.format(i))
l.add_xpath('Floor', '//li[{}]/div[2]/div[2]/span[3]/text()'.format(i))
l.add_xpath('Year', '//li[{}]/div[2]/div[2]/span[4]/text()'.format(i))
l.add_xpath('Garden', '//li[{}]/div[2]/div[3]/span/text()'.format(i))
l.add_xpath('Region', '//li[{}]/div[2]/div[3]/span/text()'.format(i))
l.add_xpath('Price', '//li[{}]/div[3]/span[1]/strong/text()'.format(i))
yield l.load_item()
安x客的反爬比较严重,如果不使用代理ip池,速度过快非常容易挂掉。而链x的反爬相对没那么严格,速度可以很快。
- ❺ -
总结
以上是对本项目爬虫部分核心内容的分享(完整代码在知识星球中),至此这个项目完成了从爬虫到数据分析,再到数据挖掘预测的 "三部曲" 完整过程。虽然这个项目比较简单,仍有很多地方需要完善,但是希望通过这个项目能让大家对整个过程有个很好的认识和了解。
推荐阅读:
【5】【Kaggle入门级竞赛top5%排名经验分享】— 建模篇
【6】【Kaggle入门级竞赛top5%排名经验分享】— 分析篇
长按二维码 关注Python数据科学发送 「学习资料」,获取经典书籍电子书